package cn.com.zero.job.core;

import cn.com.zero.job.enums.ScheduleTypeEnum;
import cn.com.zero.job.exception.JobInitException;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import com.xxl.job.core.executor.impl.XxlJobSimpleExecutor;
import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.support.CronExpression;

import java.util.*;

/**
 * @author zhoupeng
 * @version 1.0.0
 * @Description xxl-job 执行器配置类
 * @createTime 2022-07-13
 */
@Configuration
public class JobExecutorConfig implements ApplicationContextAware, SmartInitializingSingleton {

    @Value("${xxl.job.admin.addresses}")
    private String adminAddress;

    @Value("${xxl.job.accessToken:}")
    private String accessToken;

    @Value("${xxl.job.executor.appname:${spring.application.name}}")
    private String appName;

    @Value("${xxl.job.executor.address:}")
    private String address;

    @Value("${xxl.job.executor.ip:}")
    private String ip;

    @Value("${xxl.job.executor.port:0}")
    private int port;

    @Value("${xxl.job.executor.logpath:}")
    private String logPath;

    @Value("${xxl.job.executor.logretentiondays:-1}")
    private int logRetentionDays;

    /**
     * 日志类
     */
    private Logger logger = LoggerFactory.getLogger(JobExecutorConfig.class);

    /**
     * 保存任务实体类，key 为任务名称
     */
    private Map<String, ZeroJob> jobMap = new HashMap<>();

    /**
     * 服务类
     */
    private ZeroJobService zeroJobService;

    public JobExecutorConfig(ZeroJobService zeroJobService) {
        this.zeroJobService = zeroJobService;
    }

    @Bean
    public XxlJobSpringExecutor xxlJobExecutor() {
        // init executor
        XxlJobSpringExecutor xxlJobExecutor = new XxlJobSpringExecutor();
        xxlJobExecutor.setAdminAddresses(adminAddress);
        xxlJobExecutor.setAccessToken(accessToken);
        xxlJobExecutor.setAppname(appName);
        xxlJobExecutor.setAddress(address);
        xxlJobExecutor.setIp(ip);
        xxlJobExecutor.setPort(port);
        xxlJobExecutor.setLogPath(logPath);
        xxlJobExecutor.setLogRetentionDays(logRetentionDays);
        return xxlJobExecutor;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        // 获取所有继承自 ZeroJob 的实例
        Collection<ZeroJob> jobList = applicationContext.getBeansOfType(ZeroJob.class).values();

        Set<String> nameSet = new HashSet<>();
        // 添加到map
        jobList.forEach(zeroJob -> {
            // 校验任务基本参数
            validJob(zeroJob);
            // 校验任务名称是否重复
            if (nameSet.contains(zeroJob.name())) {
                throw new RuntimeException(StrUtil.format("{} 任务存在多个实例", zeroJob.name()));
            }
            nameSet.add(zeroJob.name());
            // 添加到本地表
            jobMap.put(zeroJob.name(), zeroJob);
        });
    }

    @Override
    public void afterSingletonsInstantiated() {
        logger.info("==========开始初始化 Job==========");

        // 将本地任务信息更新到数据库
        zeroJobService.updateJobs(jobMap.values());

        // 注册本地任务
        jobMap.values().forEach(job -> {
            logger.info("注册任务 {}", job.getHandlerName(appName));
            // 将任务注册到执行器
            XxlJobSimpleExecutor.registJobHandler(job.getHandlerName(appName), job);
        });

        logger.info("==========初始化完成 Job==========");
    }

    /**
     * 校验 job 合法性
     *
     * @param zeroJob job类
     */
    private void validJob(ZeroJob zeroJob) {
        Assert.notEmpty(zeroJob.name(), "{} name 不能为空", zeroJob.getClass().getName());
        Assert.notNull(zeroJob.type(), "{} type 不能为空", zeroJob.getClass().getName());
        Assert.notNull(zeroJob.config(), "{} config 不能为空", zeroJob.getClass().getName());
        Assert.notNull(zeroJob.routeStrategy(), "{} routeStrategy 不能为空", zeroJob.getClass().getName());
        Assert.notNull(zeroJob.misfireStrategy(), "{} misfireStrategy 不能为空", zeroJob.getClass().getName());
        Assert.notNull(zeroJob.blockStrategy(), "{} blockStrategy 不能为空", zeroJob.getClass().getName());

        if (zeroJob.type() == ScheduleTypeEnum.FIX_RATE) {
            if (!NumberUtil.isLong(zeroJob.config())) {
                throw new JobInitException(StrUtil.format("{} 任务初始化失败，固定速度 config 配置为整数", zeroJob.name()));
            }
        } else if (zeroJob.type() == ScheduleTypeEnum.CRON) {
            try {
                CronExpression.parse(zeroJob.config());
            } catch (IllegalArgumentException e) {
                throw new JobInitException(StrUtil.format("{} 任务初始化失败，CRON 表达式解析失败：{}", zeroJob.name(), e.getMessage()));
            }
        }

    }



}
